27. Using cast At Least

C++은 “어떤 일이 있어도 타입 에러가 생기지 않도록 보장한다.”는 동작 철학을 바탕으로 설계되어 있다.

캐스트(cast)는 C++이 민감한 타입을 변경하는 연산자로 사용에 각별히 유의한다.
C 스타일 캐스팅
T(expr)는 해당 객체를 생성하는 의미도 가지고 있다.
(T)expr; // expr T
T(expr); // expr T
C++ 스타일 캐스팅
const_cast<T>(expr);
dynamic_cast<T>(expr);
reinterpret_cast<T>(expr);
static_cast<T>(expr);
const_cast
객체의 상수성(constness) 혹은 휘발성(volatileness)를 없애는 용도로 사용

dynamic_cast
안전한 다운캐스팅(safe downcasting)을 할 때, 사용하는 연산자
주어진 객체가 어떤 클래스의 상속 계통에 속한 특정 타입인지 여부를 검사할 때 사용됨
구형 캐스팅 연산자로 구현할 수 없으며, 런타임 비용이 높음
(파생클래스 포인터를 기본클래스 포인터로 다운캐스팅하는데 사용)

reinterpret_cast
포인터를 int로 바꾸는 등의 하부 수준 캐스팅에 사용되는 연산자
적용 결과는 구현환경에 의존적임(이식성이 없다.)

static_cast
암시적 변환(비상수->상수, int->double 등)를 강제로 진행할 때, 사용되는 연산자
class widget{
public:
explicit Widget(int size);
// ...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); // int->Widget
doSomeWork(static_cast<Widget>(15)); // C++ int->Widget
C++에서는 타입 변환을 통해 런타임에 실행되는 코드생성될 수 있다.
int x, y;
// ...
double d=static_cast<double>(x)/y;
위에서 int를 double로 캐스팅할 때(런타임에) 코드가 생성된다.
(대부분의 컴퓨터 아키텍처에서 int의 표현구조는 double의 표현구조와 완전 다르다.)
class Base{/* ... */};
class Derived: public Base{/* ... */};
Derived d;
Base* pb=&d; // Derived* -> Base*
위와 같이 파생 클래스 객체에 대한 기본 클래스 포인터를 만드는 경우,
포인터는 변위(offset)를 Derived 객체에 적용하여, 실제의 Base* 포인터 값을 구하는 동작을
런타임(runtime)에 수행한다.

객체의 메모리 배치구조를 결정하는 방법과 객체의 주소(offset)을 계산하는 방법은 컴파일러에 따라 다르다.
class Window{
public:
virtual void onResize(void){ /* ... */ }
// ...
};
class SpecialWindow: public Window{
public:
virtual void onResize(void){
static_cast<Window>(*this).onResize(); // (*this) onResize
// ...
}
// ...
};
위에서 (*this)를 Window로 캐스팅하고 Window::onResize()를 호출하였다.
하지만, 캐스팅이 일어나면서 *this의 기본 클래스 부분에 대하여 사본을 생성하는데,
위에 onResize()는 해당 임시 사본에 대하여 적용이 된다.
(현재 객체에 대해서 onResize()가 호출되지 않음)

위와 같은 문제점을 해결하기 위해서는 캐스팅을 사용하면 안된다.
class SpecialWindow: public Window{
public:
virtual void onResize(void){
Window::onResize(); // Window::onResize()
// ...
}
// ...
};
dynamic_cast는 느리게 구현되어 있다.(연산을 많이 소모함)
동적 링크 지원을 동반하기 때문에 반드시 사용해야 하는 경우가 있지만, 주의해서 사용해야 한다.

파생 클래스 객체임이 분명한 클래스에 대해 파생 클래스의 함수를 호출하고 싶은데,
객체를 조작할 수 있는 수단이 기본 클래스의 포인터(혹은 참조자)밖에 없는 경우 dynamic_cast를 사용된다.
dynamic_cast 사용을 피하기 위해 아래 방법을 사용할 수 있다.

파생 클래스 객체에 대한 포인터를 컨테이너에 담아둠으로써 각 객체를 기본 클래스 인터페이스를 통해 조작할
필요를 없애 버림
class Window{/* ... */};
class SpecialWindow: public Window{
public:
void blink(void);
// ...
};
typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;
// ...
// dynamic_cast
for(VPW::iterator iter=winPtrs.begin(); iter!=winPtrs.end(); ++iter){
if(SpecialWindow* psw=dynamic_cast<SpecialWindow*>(iter->get())){
psw->blink();
}
}
// dynamic_cast
typedef std::vector<std::shared_ptr<SpecialWindow> VPSW;
VPSW windPTRS;
// ...
for(VPSW::iterator iter=winPtrs.begin(); iter!=winPtrs.end(); ++iter){
(*iter)->blink();
}
하지만, 아래의 SpecialWindow(파생 클래스)에 대한 포인터를 사용시,
Window에서 파생된 다른 파생 클래스에 대하여 컨테이너에 저장할 수는 없다.

모든 파생 클래스를 사용하기 위해서는
사용하려고 하는 조작을 모두 가상 함수 집합으로 기본 클래스에서 정의해주면 된다.
class Window{
public:
virtual void blank(void){}
// ...
};
class SpecialWindow: public Window{
public:
virtual void blink(void){ /* ... */ }
// ...
};
typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;
// ... window
for(VPW::iterator iter=winPtrs.being(); iter!=winPtrs.end(); ++iter){
(*iter)->blink();
}
폭포식(cascading) dynamic_cast
아래와 같이 dynamic_cast를 만발하는 코드는 사용해서는 안된다.
BaseClass인 Window가 수정될 때 마다 수정해야 할 수 있다.
BaseClass에 virtual로 함수를 정의해서 사용할 것
class Window{ /* ... */ };
// ...
typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;
// ...
for(VPW::iterator iter=winPtrs.begin(); iter!=winPtrs.end(); ++iter){
if(SpecialWindow1* psw1=dynamic_cast<SpecialWindow1*>(iter->get())){/* ... */}
else if(SpecialWindow2* psw2=dynamic_cast<SpecialWindow2*>(iter->get())){/*...*/}
else if(SpecialWindow3* psw3=dynamic_cast<SpecialWindow3*>(iter->get())){/*...*/}
// ...
}